package com.deschen.security.core.validate.code;

import com.deschen.security.core.validate.code.exception.ValidateCodeException;
import com.deschen.security.core.validate.code.generator.ValidateCodeGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;

import java.util.Map;

/**
 * @Author deschen
 * @Create 2019/8/21
 * @Description 封装验证码处理类共有方法
 * @Since 1.0.0
 */
// 用抽象类的好处：设置公用的方法和属性，供其被实现的功能特有类调用，避免冗余
@Slf4j
public abstract class AbstractValidateCodeProcessor<T extends ValidateCode> implements ValidateCodeProcessor {

    //操作session工具
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    @Autowired
    private Map<String, ValidateCodeGenerator> validateCodeGenerators;   // 验证码生成器集合

    @Autowired
    private ObjectMapper objectMapper;

    public SessionStrategy getSessionStrategy() {
        return sessionStrategy;
    }



    @Override
    public void process(ServletWebRequest servletWebRequest, String account) throws Exception{
        log.info("【验证码处理器】验证码创建发送");
        // 生成验证码
        T validateCode = validateCodeGenerator(servletWebRequest);
        log.info("【验证码处理器】，validateCode = {}", validateCode);
        // 保存验证码到session中
        save(servletWebRequest, validateCode, account);
        // 发送验证码
        send(servletWebRequest, validateCode);
    }

//    // todo 测试
//    @SuppressWarnings("unchecked")
//    public T getValidateCodeInSession(ServletWebRequest servletWebRequest, String account) {
//        log.info("【验证码处理器】从session中获取验证码");
//        // 根据session_key_for_code_<validateCodeTypeName>_<accountNumber>将验证码保存到session
//        return (T) sessionStrategy.getAttribute(
//                servletWebRequest,
//                getSessionKey(account)
//        );
//    }

    /**
     * 保存验证码: 根据session前缀名 + 验证码类型 + 账号名生成唯一key保存验证码到session
     * @param servletWebRequest
     * @param validateCode
     * @param account
     */
    private void save(ServletWebRequest servletWebRequest, T validateCode, String account) {
        // 根据session_key_for_code_<validateCodeTypeName>_<accountNumber>将验证码保存到session
        sessionStrategy.setAttribute(
                servletWebRequest,
                getSessionKey(account),
                validateCode
        );
    }

    /**
     * 发送验证码，因为发送方式不同，需要其他实现类来实现
     * @param request
     * @param validateCode
     */
    protected abstract void send(ServletWebRequest request, T validateCode) throws Exception;

    /**
     * 生成特有验证码，如图片验证码，短信验证码
     * @param request
     * @return
     */
    @SuppressWarnings("unchecked")  // 强制转换会有警告
    private T validateCodeGenerator(ServletWebRequest request) {
        // 获取特有的验证码生成器
        String validateCodeTypeName = getValidateCodeTypeName();
        String validateCodeGeneratorTypeName = validateCodeTypeName + ValidateCodeGenerator.class.getSimpleName();
        ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(validateCodeGeneratorTypeName);
        // 判空
        if (validateCodeGenerator == null) {
            throw new ValidateCodeException("验证码生成器不存在");
        }
        return (T) validateCodeGenerator.createValidateCode(request);
    }

    /**
     * 获取特有的验证码生成器
     * @return
     */
    private String getValidateCodeTypeName() {
        return StringUtils.substringBefore(getClass().getSimpleName(),
                    ValidateCodeProcessor.class.getSimpleName()).toLowerCase();
    }

    @SuppressWarnings("unchecked")
    @Override
    public void validate(ServletWebRequest servletWebRequest, String account) {

        String sessionKey = getSessionKey(account);

        T codeInSession = (T)sessionStrategy.getAttribute(servletWebRequest, sessionKey);

        String codeInRequest;
        String validateCodeTypeName = getValidateCodeTypeName();
        try {
            codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(),
                    validateCodeTypeName + "ValidateCode");
        } catch (ServletRequestBindingException e) {
            throw new ValidateCodeException("获取验证码的值失败");
        }

        if (StringUtils.isBlank(codeInRequest)) {
            throw new ValidateCodeException(validateCodeTypeName + "验证码的值不能为空");
        }

        if (codeInSession == null) {
            throw new ValidateCodeException(validateCodeTypeName + "验证码不存在");
        }

        if (codeInSession.isExpired()) {
            sessionStrategy.removeAttribute(servletWebRequest, sessionKey);
            throw new ValidateCodeException(validateCodeTypeName + "验证码已过期");
        }

        if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
            throw new ValidateCodeException(validateCodeTypeName + "验证码不匹配");
        }

        sessionStrategy.removeAttribute(servletWebRequest, sessionKey);
    }

    /**
     * 创建session_key_for_code_<validateCodeTypeName>_<accountNumber>，保存验证码的session的key值
     * @param account
     * @return
     */
    private String getSessionKey(String account) {
        return SESSION_KEY_PREFIX + getValidateCodeTypeName() + "_" + account;
    }

}
